package com.ksyun.campus.metaserver.biz.services;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.ksyun.campus.metaserver.common.result.BaseResult;
import com.ksyun.campus.metaserver.entity.dto.req.DataServerFixReqDto;
import com.ksyun.campus.metaserver.entity.dto.resp.DataServerAllFilesRespDto;
import com.ksyun.campus.metaserver.entity.meta.FileType;
import com.ksyun.campus.metaserver.entity.meta.ReplicaData;
import com.ksyun.campus.metaserver.entity.meta.StatInfo;
import com.ksyun.campus.metaserver.entity.server.DataServer;
import com.ksyun.campus.metaserver.util.HttpRemoteUtil;
import com.ksyun.campus.metaserver.util.NetworkUtils;
import com.ksyun.campus.metaserver.util.ZooKeeperUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class FsckServices {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    private final ZooKeeperUtil zooKeeperUtil;
    private DataServerAllFilesRespDto dataServer9000;

    private DataServerAllFilesRespDto dataServer9002;
    private DataServerAllFilesRespDto dataServer9003;
    private DataServerAllFilesRespDto dataServer9004;

    //    @Scheduled(cron = "0 0 0 * * ?") // 每天 0 点执行
//    @Scheduled(fixedRate = 30 * 60 * 1000) // 每隔 30 分钟执行一次
    public List<StatInfo> fsckTask() {
        List<StatInfo> deadStatInfos = new ArrayList<>();
        Set<String> fileSystemNames = stringRedisTemplate.keys("*");
        fileSystemNames.forEach(fileSystemName -> {
            // 初始化当前命名空间所有ds上的文件
            getAllDataServerAllFiles(fileSystemName);
            // todo 全量扫描文件列表
            // todo 检查文件副本数量是否正常
            // todo 更新文件副本数：3副本、2副本、单副本
            // todo 记录当前检查结果
            deadStatInfos.addAll(listAll(fileSystemName));
        });
        return deadStatInfos;

    }


    /**
     * 从根目录 / 开始遍历所有文件夹和文件，按层级顺序获取它们的 StatInfo 数据
     *
     * @param fileSystemName 文件系统名称
     * @return 包含所有文件夹和文件的 StatInfo 列表
     */
    public List<StatInfo> listAll(String fileSystemName) {
        List<StatInfo> result = new ArrayList<>();
        traverseDirectory(fileSystemName, "/", result);
        return result;
    }

    /**
     * 递归遍历目录及其子目录，按层级顺序添加 StatInfo 到结果列表
     *
     * @param fileSystemName 文件系统名称
     * @param currentPath    当前处理的路径
     * @param result         存储结果的列表
     */
    private void traverseDirectory(String fileSystemName, String currentPath, List<StatInfo> result) {

        try (Cursor<Map.Entry<Object, Object>> cursor = stringRedisTemplate.opsForHash().scan(fileSystemName, ScanOptions.scanOptions().match(currentPath + "*").build())) {
            // 用于存储当前层级的子节点
            Map<String, String> currentLevelNodes = new TreeMap<>();

            while (cursor.hasNext()) {
                Map.Entry<Object, Object> entry = cursor.next();
                String fullPath = (String) entry.getKey();

                // 排除自己
                if (currentPath.equals(fullPath)) continue;

                // 计算相对路径
                String relativePath = fullPath.substring(currentPath.length());
                if (relativePath.startsWith("/")) {
                    relativePath = relativePath.substring(1);
                }

                // 仅处理当前层级的节点
                if (!relativePath.contains("/")) {
                    currentLevelNodes.put(fullPath, (String) entry.getValue());
                }
            }

            // 按顺序处理当前层级的所有节点
            for (Map.Entry<String, String> entry : currentLevelNodes.entrySet()) {
                String fullPath = entry.getKey();
                String value = entry.getValue();
                StatInfo statInfo = JSON.parseObject(value, StatInfo.class);

                /**
                 * 检查当前节点副本情况
                 */
                List<ReplicaData> replicaData = statInfo.getReplicaData();
                String path = statInfo.getPath();
                System.out.println(path);
                FileType fileType = statInfo.getType();

                // 每个副本发送请求检查
                replicaData.forEach(eachReplicate -> {
                    String host = eachReplicate.getDsNode();
                    Integer port = Integer.parseInt(host.split(":")[1]);
                    switch (port) {
                        case 9000:
                            check(fileSystemName, dataServer9000, fileType, path, statInfo, host, port, result);
                            break;
                        case 9002:
                            check(fileSystemName, dataServer9002, fileType, path, statInfo, host, port, result);
                            break;
                        case 9003:
                            check(fileSystemName, dataServer9003, fileType, path, statInfo, host, port, result);
                            break;
                        case 9004:
                            check(fileSystemName, dataServer9004, fileType, path, statInfo, host, port, result);
                            break;
                    }
                });

                // 如果是目录，递归处理子目录
                if (statInfo.getType().equals(FileType.Directory)) {
                    traverseDirectory(fileSystemName, fullPath + "/", result);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取所有四个节点所有文件数据
     */
    public void getAllDataServerAllFiles(String fileSystemName) {
        List<DataServer> dataServerlist =
                zooKeeperUtil.getDataServerlist(zooKeeperUtil.ZK_DATA_SERVER_ROOT_PATH);
        dataServerlist.forEach(each -> {
            String ip = each.getIp();
            Integer port = each.getPort();
            String dataServerAllFiles = HttpRemoteUtil.get(ip + ":" + port, "/data/getAllFiles", null)
                    .header("fileSystemName", fileSystemName)
                    .execute()
                    .body();
            BaseResult<DataServerAllFilesRespDto> response
                    = JSON.parseObject(dataServerAllFiles, new TypeReference<BaseResult<DataServerAllFilesRespDto>>() {
            });
            DataServerAllFilesRespDto curDataServerAllFiles = response.getData();
            switch (port) {
                case 9000:
                    dataServer9000 = curDataServerAllFiles;
                    break;
                case 9002:
                    dataServer9002 = curDataServerAllFiles;
                    break;
                case 9003:
                    dataServer9003 = curDataServerAllFiles;
                    break;
                case 9004:
                    dataServer9004 = curDataServerAllFiles;
                    break;
            }
        });
    }

    /**
     * 检查修复节点
     */
    public void check(String fileSystemName, DataServerAllFilesRespDto dataServerAllFiles, FileType fileType, String path, StatInfo statInfo, String host, Integer port, List<StatInfo> result) {
        // 查看dataServer上是否有当前文件
        if (fileType.equals(FileType.File)) {
            // 无 则该修复了
            DataServer alive = getAliveFileDataServerPort(fileSystemName, path, FileType.File.name());
            getAliveFileDataServerPort(fileSystemName, path, FileType.File.name());
            // 获取ds服务上所有文件为null，说明服务可能宕机了
            if (ObjectUtil.isNull(dataServerAllFiles)) {
                log.error("无所有文件数据，ds服务可能宕机了 [host]: {}", host);
                return;
            }
            // 发送修复请求
            if (!dataServerAllFiles.getFileList().contains("/" + fileSystemName + path)) {
                log.info("开始发送修复请求: [DadaServerHost]{}", host);
                DataServerFixReqDto reqDto =
                        DataServerFixReqDto.builder()
                                .path(path)
                                .existFileList(alive)
                                .build();
                String responseJson = HttpRemoteUtil.post(host, "/data/fixFiles")
                        .body(JSON.toJSONString(reqDto))
                        .header("fileSystemName", fileSystemName)
                        .execute()
                        .body();
                BaseResult<?> response = JSON.parseObject(responseJson, new TypeReference<BaseResult<?>>() {
                });
                if (response.getCode() == 200) {
                    log.info("修复节点成功 [StatInfo]: {} [port]: {}", path, port);
                    result.add(statInfo);
                } else {
                    log.error("修复节点异常 [msg]: {}", response.getMsg());
                }
            }
        } else if (fileType.equals(FileType.Directory)) {
            // 无 则该修复了
            DataServer alive = getAliveFileDataServerPort(fileSystemName, path, FileType.Directory.name());
            // todo 可能所有节点都没有当前文件 alive为null
            // getAliveFileDataServerPort(fileSystemName, path, FileType.Directory.name());
            // 获取ds服务上所有文件为null，说明服务可能宕机了
            if (ObjectUtil.isNull(dataServerAllFiles)) {
                log.error("无所有文件数据，ds服务可能宕机了 [host]: {}", host);
                return;
            }
            if (!dataServerAllFiles.getDirList().contains("/" + fileSystemName + path)) {
                log.info("开始发送修复请求: [DadaServerHost]{}", host);
                DataServerFixReqDto reqDto =
                        DataServerFixReqDto.builder()
                                .path(path)
                                .existFileList(alive)
                                .build();
                String responseJson = HttpRemoteUtil.post(host, "/data/fixFiles")
                        .body(JSON.toJSONString(reqDto))
                        .header("fileSystemName", fileSystemName)
                        .execute()
                        .body();
                BaseResult<?> response = JSON.parseObject(responseJson, new TypeReference<BaseResult<?>>() {
                });
                if (response.getCode() == 200) {
                    log.info("修复节点成功 [StatInfo]: {} [port]: {}", path, port);
                    result.add(statInfo);
                } else {
                    log.error("修复节点异常 [msg]: {}", response.getMsg());
                }
            }
        }
    }

    /**
     * 从所有DataServer节点的所有文件数据中挑选一个活的节点
     */
    public DataServer getAliveFileDataServerPort(String fileSystemName, String path, String fileType) {
        List<DataServer> dataServerlist =
                zooKeeperUtil.getDataServerlist(zooKeeperUtil.ZK_DATA_SERVER_ROOT_PATH);
        String localIp = NetworkUtils.getLocalIp();
        path = "/" + fileSystemName + path;
        if (FileType.File.name().equals(fileType)) {
            if (dataServer9000 != null && dataServer9000.getFileList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9000)).findFirst().orElse(null);
            if (dataServer9002 != null && dataServer9002.getFileList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9002)).findFirst().orElse(null);
            if (dataServer9003 != null && dataServer9003.getFileList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9003)).findFirst().orElse(null);
            if (dataServer9004 != null && dataServer9004.getFileList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9004)).findFirst().orElse(null);
        }
        if (FileType.Directory.name().equals(fileType)) {
            if (dataServer9000 != null && dataServer9000.getDirList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9000)).findFirst().orElse(null);
            if (dataServer9002 != null && dataServer9002.getDirList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9002)).findFirst().orElse(null);
            if (dataServer9003 != null && dataServer9003.getDirList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9003)).findFirst().orElse(null);
            if (dataServer9004 != null && dataServer9004.getDirList().contains(path))
                return dataServerlist.stream().filter(dataServer -> dataServer.getPort().equals(9004)).findFirst().orElse(null);
        }
        // 当前文件无活的节点
        return null;
    }
}
